Skip to content

feat(mobile): queue/send from the keyboard Return key (#7, #8) - #16

Merged
radroid merged 2 commits into
mainfrom
t3x/mobile-keyboard-queue
Jul 26, 2026
Merged

feat(mobile): queue/send from the keyboard Return key (#7, #8)#16
radroid merged 2 commits into
mainfrom
t3x/mobile-keyboard-queue

Conversation

@radroid

@radroid radroid commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #8, closes #7. On mobile there was no keyboard way to queue/send a message to a working thread — the soft-keyboard Return inserted a newline, and submit was only wired to hardware ⌘-Return on iOS (and not at all on Android).

Note on #7: its premise is outdated. Queueing already works end-to-end today — every "send" enqueues to the thread outbox (sends-when-idle, holds-when-busy), the primary button already relabels to "Queue" while busy, and a queueCount indicator already shows pending messages. The only unmet criterion across #7/#8 was the keyboard trigger, which this PR adds — so it closes both.

Changes

  • iOS (T3ComposerEditorView.swift): intercept a bare "\n" in the shouldChangeText delegate → fire onComposerSubmit (soft keyboard or hardware Return). Exact single "\n" only, so a multi-line paste ("a\nb") is unaffected; the existing ⌘-Return keyCommand is untouched.
  • Android (T3ComposerEditorView.kt / Module.kt): wire onComposerSubmit for the first time — a new event, plus a plain-Enter interception on both the hardware key-event path (onKeyDown) and the soft-keyboard commitText("\n") path, with Shift+Enter still inserting a newline. TYPE_TEXT_FLAG_MULTI_LINE preserved.
  • RN wrapper (T3ComposerEditor.native.tsx): forward onSubmit → onComposerSubmit (previously dropped on Android; iOS already forwarded it).
  • Composer (ThreadComposer.tsx): Return routes to the existing handleSend (queue-when-busy / send-when-idle), so queueCount updates identically to the button. Adds a dedicated "Insert line break" toolbar button (inserts "\n" at the caret via the same replaceTextRange helper used for @file/skill tokens) as the cross-platform newline affordance now that Return submits.
  • Extracts resolveComposerSendLabel as a pure, unit-tested helper.

Trade-off (intended): hardware plain-Return now submits instead of inserting a newline (matching desktop Enter=submit). Newline remains available via the toolbar button and Shift+Return.

Testing

  • TS unit (TDD, red-first): composerSendLabel (Send/Queue matrix) and replaceTextRange newline cases (caret mid-text, at end, replacing a selection). → all green (57 threads/state + 10 shared).
  • Typecheck: no new errors in touched files (the repo's pre-existing React-Navigation never errors are unrelated).
  • Native static analysis (CI's lint:mobile): swiftlint --strict 0 violations, ktlint + detekt pass.
  • iOS Simulator: built & launched on iPhone 17 (see PR comment for result).

⚠️ Needs on-device QA (cannot be unit-tested)

  • Android: confirm plain-Enter submits with no double-enqueue across IMEs (the key-event and commitText paths are designed to be mutually exclusive, but this is IME-dependent), Shift+Enter inserts a newline, and the newline button (controlled-value setText) is not caught by the commitText interception.
  • iOS: confirm soft-keyboard Return submits, ⌘-Return still submits, multi-line paste still inserts.

🤖 Generated with Claude Code

Fixes #8. Closes #7.

Mobile queueing already worked end-to-end (every send enqueues to the thread
outbox, which sends-when-idle and holds-when-busy; the primary button already
relabels to "Queue" while busy and a queueCount indicator exists). The only gap
was the keyboard: the soft-keyboard Return inserted a newline, and submit was
only wired to hardware Command-Return on iOS (and not at all on Android).

- iOS: intercept a bare "\n" in the shouldChangeText delegate and fire
  onComposerSubmit (soft keyboard or hardware Return). Exact single "\n" only,
  so a multi-line paste ("a\nb") is unaffected; Command-Return keyCommand intact.
- Android: wire onComposerSubmit for the first time — an onComposerSubmit event,
  plus a plain-Enter interception on both the key-event path (onKeyDown) and the
  IME commitText path, with Shift+Enter still inserting a newline. Multi-line
  input type preserved.
- RN: forward onSubmit -> onComposerSubmit through the Android wrapper (was
  dropped); iOS wrapper already forwarded it.
- Composer: Return routes to the existing handleSend (queue-when-busy /
  send-when-idle), so queueCount updates the same way as the button. Add a
  dedicated "Insert line break" toolbar button (inserts "\n" at the caret via
  the same replaceTextRange helper used for @file / skill tokens) as the
  cross-platform newline affordance now that Return submits.
- Extract resolveComposerSendLabel as a pure, unit-tested helper.

Trade-off: hardware plain-Return now submits instead of inserting a newline
(matching desktop Enter=submit); the newline button and Shift+Return cover
multiline.

Tests: composerSendLabel (Send/Queue matrix) and replaceTextRange newline cases.
Native keyboard behavior verified on the iOS Simulator; Android soft-keyboard
paths need on-device QA (noted on the PR).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 942d91ae-932e-4cf3-b698-ba8f21143e07

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3x/mobile-keyboard-queue

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Review follow-up: a bare Return now submits, but Shift+Return was also
delivering "\n" through the shouldChangeText delegate and submitting —
inconsistent with Android (Shift+Enter inserts a newline) and with this PR's
own description. Add a Shift+Return UIKeyCommand that inserts a line break via
an `isInsertingHardLineBreak` flag the delegate honors, so hardware
Shift+Return matches Android and desktop Shift+Enter. Soft keyboards still use
the composer's line-break button.

Verified: swiftlint --strict clean; iOS Simulator (iPhone 17 Pro) build
succeeds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@radroid

radroid commented Jul 26, 2026

Copy link
Copy Markdown
Owner Author

Verification summary

Opus 5 review → APPROVE (no blockers). Traced every path across Swift/Kotlin/TS; confirmed:

  • iOS bare-"\n" intercept doesn't swallow multi-line paste; ⌘-Return path is separate (no double-fire); the newline button flows through the controlled value (setText/attributedText), never the typing path; empty composer no-ops.
  • Android double-submit is not reachable: the key-event and commitText channels are effectively mutually exclusive, and handleSend's inFlightThreadIdsRef guard collapses any duplicate submits into a single enqueue.

Applied one review follow-up (511d5c76a): iOS Shift+Return now inserts a newline (was submitting) — consistent with Android/desktop.

Checks

  • TS unit tests (TDD): 57 (threads/state) + 10 (shared) green.
  • Typecheck: no new errors in touched files.
  • Native static analysis: swiftlint --strict 0 violations, ktlint + detekt pass.
  • iOS Simulator (iPhone 17 Pro): full xcodebuild** BUILD SUCCEEDED **, and a fast incremental rebuild after the Shift+Return fix also succeeded. This validates the Swift change compiles and links. Interactive keyboard behavior (soft Return submitting, Shift+Return newline, no Android double-enqueue across OEM IMEs) still needs a signed-in on-device pass — the composer only exists inside an authenticated thread, unreachable from an autonomous Simulator run.

lint:mobile may look red locally if apps/mobile/ios/ was generated by expo prebuild (2 pre-existing swiftlint issues in the generated AppDelegate.swift). CI does not prebuild, so ios/ is absent and the gate skips it — verified node scripts/mobile-native-static-check.ts exits 0 without a prebuilt ios/.

@radroid
radroid merged commit 4ab11db into main Jul 26, 2026
1 check passed
@radroid
radroid deleted the t3x/mobile-keyboard-queue branch July 30, 2026 14:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mobile: no keyboard way to queue a message while a thread is working Allow queueing messages to a working thread on mobile

1 participant